package me.seawenc.db.checker.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import me.seawenc.db.checker.dbengine.DsType;
import me.seawenc.db.checker.dbengine.IEngineService;
import me.seawenc.db.checker.dbengine.impl.engines.bean.TableColumnBean;
import me.seawenc.db.checker.dbengine.impl.engines.bean.TableDetailBean;
import me.seawenc.db.checker.dbengine.impl.engines.bean.TableNameBean;
import me.seawenc.db.checker.bean.FileConfig;
import me.seawenc.db.checker.bean.ExecParameters;
import me.seawenc.db.checker.bean.CheckMetaBean;
import me.seawenc.db.checker.helper.FileHelper;
import me.seawenc.db.checker.helper.Log;
import me.seawenc.db.checker.helper.Optionalx;

import java.util.*;
import java.util.stream.Collectors;

public class SchemaService {

    /**
     * 元数据导出
     * @param parameters
     */
    public static void export(ExecParameters parameters) {
        try {
            // 生成元数据对象
            CheckMetaBean checkMeta = createCheckMeta(parameters);
            // 导出元数据到文件
            FileHelper.writeToFile(parameters, JSON.toJSONString(checkMeta));
            Log.info("0.从数据库中获得表总数为：%s,配置表数为：%s",checkMeta.getTableMetaMap().size(),checkMeta.getConfTableItemMap().size());
            Log.info("1.元数据对象生成成功，当前数据库版本:%s,元数据文件路径：%s",checkMeta.getDbVersion(),parameters.getCheckPath());
        } catch (Exception e) {
            Log.error(e.getMessage(),e);
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 对比元数据
     * @param parameters
     */
    public static void check(ExecParameters parameters) {
        try {
            // 生成元数据对象
            CheckMetaBean target = createCheckMeta(parameters);
            // 读取元数据json文件
            CheckMetaBean src=readFromFile(parameters.getCheckPath());
            // 对比两个元数据，打印出差异
            diffMetaAndPrint(target,src);
        } catch (Exception e) {
            Log.error(e.getMessage(),e);
            throw new RuntimeException(e.getMessage());
        }
    }

    private static CheckMetaBean readFromFile(String checkPath) throws Exception {
        String metaStr = FileHelper.readExtFile(checkPath).stream().collect(Collectors.joining("\n"));
        CheckMetaBean meta = JSONObject.parseObject(metaStr, CheckMetaBean.class);
        return meta;
    }

    private static void diffMetaAndPrint(CheckMetaBean target, CheckMetaBean src) {
        Log.info("0.从`数据库/json文件`中获得表总数为：`%s/%s`,配置表数为：`%s/%s`",target.getTableMetaMap().size(),src.getTableMetaMap().size(),target.getConfTableItemMap().size(),src.getConfTableItemMap().size());
        Log.info("1.目标库版本:%s,json文件中的数据库版本：%s,json文件生成时间：%s",target.getDbVersion(),src.getDbVersion(),src.getDatetime());
        Log.info("2.schema元数据对比结果：（若无日志，则表示完全一致）=================================");
        diffTableMeta(target, src);
        Log.info("3.conf表配置项对比结果：（若无日志，则表示完全一致）=================================");
        diffConf(target, src);
        Log.info("4.检查结束================================================================");
    }

    private static void diffConf(CheckMetaBean target, CheckMetaBean src) {
        {// 检查配置表是否一致
            Set<String> targetConfKeys = target.getConfTableItemMap().keySet();
            Set<String> srcConfKeys = src.getConfTableItemMap().keySet();
            String srcNotExist = targetConfKeys.stream().filter(tablename -> !srcConfKeys.contains(tablename)).collect(Collectors.joining(","));
            String targetNotExist = srcConfKeys.stream().filter(tablename -> !targetConfKeys.contains(tablename)).collect(Collectors.joining(","));
            //打印出找出的差异
            Log.condWarn(Optionalx.isPresent(srcNotExist),String.format("conf table=%s，配置表比对：目标库中已配置，生成json文件时未配置",srcNotExist));
            Log.condWarn(Optionalx.isPresent(targetNotExist),String.format("conf table=%s，配置表比对：目标库中未配置，生成json文件时已配置",targetNotExist));
        }

        // 检查各表的配置项是否一致
        for(Map.Entry<String, List<String>> targetEntry: target.getConfTableItemMap().entrySet()){
            List<String> srcTableInfo = src.getConfTableItemMap().get(targetEntry.getKey());
            if(Optionalx.isNotPresent(srcTableInfo)){
                //此种情况已经检查过了
                continue;
            }
            String srcNotExist = targetEntry.getValue().stream().filter(name -> !srcTableInfo.contains(name)).collect(Collectors.joining(","));
            String targetNotExist = srcTableInfo.stream().filter(name -> !targetEntry.getValue().contains(name)).collect(Collectors.joining(","));
            // 打印出找出的差异
            Log.condWarn(Optionalx.isPresent(srcNotExist),String.format("conf table=%s，值对比：目标库中存在，json文件中不存在的字段有：%s",targetEntry.getKey(),srcNotExist));
            Log.condWarn(Optionalx.isPresent(targetNotExist),String.format("conf table=%s，值对比：json文件中存在，目标库中不存在的字段有：%s",targetEntry.getKey(),targetNotExist));
        }
    }

    private static void diffTableMeta(CheckMetaBean target, CheckMetaBean src) {
        {// 检查表数量是否一致，打印出差异
            Set<String> targetConfKeys = target.getTableMetaMap().keySet();
            Set<String> srcConfKeys = src.getTableMetaMap().keySet();
            String srcNotExist = targetConfKeys.stream().filter(tablename -> !srcConfKeys.contains(tablename)).collect(Collectors.joining(","));
            String targetNotExist = srcConfKeys.stream().filter(tablename -> !targetConfKeys.contains(tablename)).collect(Collectors.joining(","));
            //打印出找出的差异
            Log.condWarn(Optionalx.isPresent(srcNotExist),String.format("表比对：目标库中存在，json文件中不存在的表有：%s",srcNotExist));
            Log.condWarn(Optionalx.isPresent(targetNotExist),String.format("表比对：json文件中存在，目标库中不存在的表有：%s",targetNotExist));
        }

        // 检查各表的字段项是否一致
        for(Map.Entry<String, List<TableColumnBean>> targetEntry: target.getTableMetaMap().entrySet()){
            List<TableColumnBean> srcTableInfo = src.getTableMetaMap().get(targetEntry.getKey());
            if(Optionalx.isNotPresent(srcTableInfo)){
                //此种情况已经检查过了
                continue;
            }
            List<String> srcColumnNames = srcTableInfo.stream().map(i -> i.getColumnName()).collect(Collectors.toList());
            List<String> targetColumnNames = targetEntry.getValue().stream().map(i -> i.getColumnName()).collect(Collectors.toList());

            String srcNotExist = targetColumnNames.stream().filter(name -> !srcColumnNames.contains(name)).collect(Collectors.joining(","));
            String targetNotExist = srcColumnNames.stream().filter(name -> !targetColumnNames.contains(name)).collect(Collectors.joining(","));
            // 打印出找出的差异
            Log.condWarn(Optionalx.isPresent(srcNotExist),String.format("table=%s,字段比对：目标库中存在，json文件中不存在的字段有：%s",targetEntry.getKey(),srcNotExist));
            Log.condWarn(Optionalx.isPresent(targetNotExist),String.format("table=%s,字段比对：json文件中存在，目标库中不存在的字段有：%s",targetEntry.getKey(),targetNotExist));
        }
    }

    private static CheckMetaBean createCheckMeta(ExecParameters parameters) throws Exception {
        FileConfig fileConfig = createFileParameters(parameters.getConfigPath());
        IEngineService dbEngine = DsType.findDbEngine(fileConfig);
        CheckMetaBean checkMetaBean=new CheckMetaBean();
        checkMetaBean.setDbVersion(dbEngine.findVersion());
        buildTableMeta(dbEngine, checkMetaBean);
        buildConfItem(dbEngine, checkMetaBean,fileConfig.getConfTableCheckSqls());
        dbEngine.closeDbCollection();

        return checkMetaBean;
    }

    private static void buildConfItem(IEngineService dbEngine, CheckMetaBean meta, Map<String,String> sqls) {
        for(Map.Entry<String,String> entry:sqls.entrySet()){
            try {
                Set<String> items = dbEngine.execSelectSql(entry.getValue()).stream().map(row->row.getString("item")).collect(Collectors.toSet());
                meta.getConfTableItemMap().computeIfAbsent(entry.getKey(),(name)-> new ArrayList<>()).addAll(items);
            } catch (Exception e) {
                Log.error("执行配置文件配置项检查sql失败，%s,sql=%s",entry.getKey(),entry.getValue());
            }
        }
    }

    private static void buildTableMeta(IEngineService dbEngine, CheckMetaBean checkMetaBean) throws Exception {
        List<TableNameBean> allTables = dbEngine.findAllTables();
        for(TableNameBean tableNameBean:allTables){
            TableDetailBean tableDetail = dbEngine.findTableDetail(tableNameBean.getTableName());
            checkMetaBean.getTableMetaMap().computeIfAbsent(tableNameBean.getTableName(),(name)->new ArrayList<>()).addAll(tableDetail.getTableColumns());
        }
    }

    private static FileConfig createFileParameters(String filePath) throws Exception {
        Properties props = FileHelper.readExtResFile(filePath);
        return FileConfig.getInstance(props);
    }
}
